package main

import (
	"fmt"
	"game/primaryServer"
	"log"
	"net/rpc"
	"os"
)

type Player struct {
	Id   string
	Ip   string
	Port string
}

type InitReq struct {
}

type InitRes struct {
	N       int
	K       int
	Players []Player
}

func main() {
	// talk to tracker and receive player list (might be stale)
	if len(os.Args) != 4 {
		log.Fatal("Wrong number of parameters...exiting")
	}

	ip := os.Args[1]
	port := os.Args[2]
	id := os.Args[3]

	conn, err := rpc.DialHTTP("tcp", ip+":"+port)
	if err != nil {
		log.Fatal(err)
	}

	var res InitRes
	connectTracker(trackerAddr, id, &res)

	// connect to primary server
	playerAddr := res.Players[len(res.Players)-1].PlayerAddr
	player := newPlayer(id, playerAddr)
	// listen to msg from primary server and ping from tracker
	initServer := server.InitServer(player.Id, player.playerAddr)
	localGameState := types.GameState{}

	flag := false
	for _, p := range res.Players[:len(res.Players)-1] {
		fmt.Println("connecting ", p.PlayerId)
		// connect with existing players
		err := player.sendToServer(p.PlayerAddr, "join", &localGameState)
		if err == nil {
			flag = true
			break
		} else {
			fmt.Println(err)
		}
	}
	// if all connection fails, create primary server
	if !flag {
		go initServer.NewPrimaryServer(res.K, res.N)
		time.Sleep(2 * time.Millisecond)

		player.sendToServer(player.playerAddr, "join", &localGameState)
	}

	player.localGameState = &localGameState

	for {
		var input int
		fmt.Scan(&input)
		switch input {
		case 0:
			fmt.Println("input: 0")
			go sendMessageToServer(id, "refresh")
		case 1:
		case 2:
		case 3:
		case 4:
		case 9:
		default:
		}
	}

}

func newPlayer(id string, playerAddr string) *Player {
	return &Player{
		Id:         id,
		playerAddr: playerAddr,
	}
}

// join game: send id and addr
// regenerate server:
func connectTracker(trackerAddr string, id string, res *InitRes) error {
	// write
	conn, err := net.Dial("tcp", trackerAddr)
	if err != nil {
		fmt.Println("connect tracker failed")
		return err
	}
	defer conn.Close()

	msg := types.ReqToTracker{
		Type: "init",
		Data: []byte(id),
	}
	conn.Write(utils.Marshal(msg))

	// read
	buffer := make([]byte, 8192)
	n, readErr := conn.Read(buffer)
	if readErr != nil && readErr != io.EOF {
		log.Fatal(readErr)
	} else if readErr == io.EOF {
		log.Println("connection is closed")
		return nil
	}
	if err := json.Unmarshal(buffer[:n], &res); err != nil {
		return err
	}

	return nil
}

func (player *Player) sendMessageToServer(Type string, response *types.GameState) {
	// if primary failed, send to everyone, only backup response
	timer := time.AfterFunc(time.Millisecond*2100, func() {
		for _, p := range response.Players {
			addr := p.PlayerAddr
			go player.sendToServer(addr, Type, response)
		}
	})

	// send to primary
	if err := player.sendToServer(player.localGameState.PrimaryServer.PlayerAddr, Type, response); err == nil && !timer.Stop() {
		<-timer.C
	}
}

func (player *Player) sendToServer(serverAddr string, Type string, response *types.GameState) error {
	// timeout: 0.7s, reserve time for backup
	d := net.Dialer{Timeout: time.Millisecond * 700}
	conn, err := d.Dial("tcp", serverAddr)
	if err != nil {
		return err
	}
	defer conn.Close()

	var data any
	if Type == "join" {
		data = types.PlayerAddr{
			PlayerAddr: player.playerAddr,
			PlayerId:   player.Id,
		}
	}

	msg := types.ReqToServer{
		Type: Type,
		Id:   player.Id,
		Data: utils.Marshal(data),
	}

	conn.Write(utils.Marshal(msg))

	buffer := make([]byte, 8192)
	n, readErr := conn.Read(buffer)
	if readErr != nil && readErr != io.EOF {
		log.Fatal(readErr)
	} else if readErr == io.EOF {
		log.Println("connection is closed")
		return nil
	}
	if err := json.Unmarshal(buffer[:n], &response); err != nil {
		return err
	}

	fmt.Println("success: ", response.Test)
	return nil
}
